package com.cskaoyan.javase.oop3._2inner._6lambda._0introduction;

/**
 * Lambda 表达式是 JDK8 的一个新特性.
 *
 * Lambda 表达式可以取代接口的匿名内部类，写出更优雅的Java 代码。
 * 如果说匿名内部类实际上是局部内部类的更进一步，简化了局部内部类
 * 那么Lambda就是匿名内部类更进一步
 * 语法上更简洁了，代码更优雅了，是高端的玩法，是人上人的玩法。
 *
 * 以上描述总体就说了三点：
 *      1.Lambda表达式仍然是局部内部类，是特殊的局部内部类，仍然定义在局部位置。而且局部内部类的注意事项，也一样存在。
 *      访问作用域内部类的局部变量,该局部变量是一个常量
 *      2.Lambda表达式在取代匿名内部类时，不是全部都取代，而是取代接口的匿名内部类，而类的匿名内部类Lambda表达式是不能取代的。
 *      3.Lambda表达式是匿名内部类的更进一步， Lambda表达式得到的也不是一个类，而是一个对象，并且是接口的子类对象。
 *
 * Lambda表达式在语法上,可以取代接口的匿名内部类,但是要注意并不是所有的接口
 * 都可以用Lambda表达式创建子类对象.
 * Lambda表达式的使用,对接口有特殊要求:
 *      Lambda表达式要求接口必须有且只有一个必须强制子类实现的抽象方法.
 *      这种接口,在Java语法中,称之为"功能接口(Functional Interface)"
 * 功能接口,在Java语法中,有特殊的注解来标记它:
 *      使用注解@FunctionalInterface,写在接口定义的上面,用来标记检验一个接口是否是功能接口
 *      如果是就通过编译,如果不是,就会编译报错
 * 关于功能接口,需要注意:
 *      1.功能接口中只能有一个方法吗?
 *          并不是,Java8以后接口可以定义实现方法,这些实现方法不需要强制子类实现
 *      2.功能接口中只能有一个抽象方法吗?
 *          不是,功能接口中允许定义多个抽象方法
 *          因为某些特殊的抽象方法,不需要强制子类实现
 *          Object类是Java中每一个类的父类,其中的方法被每一个类继承
 *          那么:
 *          如果一个抽象方法,可以用Object类当中存在的方法作为默认实现
 *              那么这个抽象方法就无须强制子类实现了
 *  当然,在开发中,最常见的功能接口大多只有一个方法,且就是抽象方法
 *
 *  功能接口具备以后,就可以按照下列Lambda表达式的语法,创建功能接口的子类对象了:
 *  语法:
 *  (形参列表) -> {
 *      // 方法体
 *  }
 *  解释:
 *      1."(形参列表)"是功能接口中,那个强制要求子类实现的抽象方法的形参列表,注意是直接照抄
 *         如果抽象方法的形参是空,这里也保持空着
 *         如果抽象方法有形参,这里就保持一致,照抄(形参的名字无所谓)
 *       2."->"是Lambda表达式的运算符,读作"goes to",是固定语法格式
 *       (重点)
 *       3."{}"Lambda表达式虽然表示创建功能接口的子类对象,但是这个"{}"并不是匿名子类的类体
 *          而是功能接口中,那个强制子类实现的抽象方法的重写方法体
 *          既然"{}"是方法体,所以Lambda表达式所表示的匿名子类对象是无法自定义自身成员的
 *          "{}"里面定义的都是局部变量
 *      解释一个问题: 为什么Lambda表达式要求接口是功能接口?为什么要求接口只有一个必须要实现的抽象方法?
 *      因为Lambda表达式只有一套形参列表和方法体,只能重写一个抽象方法
 *
 * 直接按照上述语法,在局部位置,创建功能接口的子类对象,但是编译报错了,为什么?
 * Java是强类型语言,任何变量都应该明确它的数据类型
 * 直接写Lambda表达式,编译器是无法明确Lambda表达式所创建对象的具体类型的,所以在这种情况下,就需要特殊的语法来使得编译器能够得知Lambda表达式所创建的子类对象,究竟是哪个功能接口的子类对象
 * 这个过程,称之为"Lambda表达式的类型推断"
 *
 * Lambda表达式的类型推断主要有以下几种方式:
 * 需要写代码提供额外信息,帮助编译器类型推断
 *      1.最简单,最容易想到的是,直接用父接口引用接收子类对象
 *      注意这时父类引用指向子类对象是不会出现无法访问子类独有成员的问题,因为Lambda表达式无法自定义子类独有成员
 *
 *      2.不常见,了解,属于Lambda表达式类型推断的特殊语法,类似于强转的语法:
 *      ((父接口的名字)Lambda表达式).调用方法();
 *      这个语法比较复杂,了解就可以,不推荐使用
 *      3.最常用的,最常见的类型推断方式,借助方法来完成类型推断:
 *          a.借助方法的形参数据类型(最最常用的)
 *          b.借助方法的返回值类型
 *
 * 以上就是Lambda表达式的基本使用了!
 *
 * @since 15:47
 * @author wuguidong@cskaoyan.onaliyun.com
 */
public class Demo {
    public static void main(String[] args) {
        // 用Lambda表达式实现接口ITest的子类对象
        // 父类引用指向子类对象
        ITest it = () -> {
            System.out.println("hello world!");
        };
        it.test();

        ((ITest) (() -> {
            System.out.println("我是Lambda表达式,我是人上人的玩法!");
        })).test();

        // 借助方法的形参数据类型完成类型推断
        method(() -> {
            System.out.println("借助方法形参完成类型推断!");
        });
        ITest it2 = method();
        it2.test();
    }

    public static ITest method() {
        return () -> {
            System.out.println("借助方法的返回值类型完成类型推断!");
        };
    }

    public static void method(ITest it) {
        it.test();
    }
}

// 在开发中,最常见的功能接口大多只有一个方法,且就是抽象方法
@FunctionalInterface
interface ITest {
    void test();
}

class ICImpl implements IC {
    @Override
    public void test() {

    }
}

@FunctionalInterface
interface IC {
    void test();

    default void test2() {
    }

    static void test3() {
    }

    // 定义可以将Object类当中的方法作为实现的抽象方法
    int hashCode();

    boolean equals(Object o);
}

// @FunctionalInterface
// interface IB{
//     void test();
//     void test2();
// }

// @FunctionalInterface
// interface IA{}